﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Drawing;
using System.Drawing.Imaging;

//TODO: check: Bresenham, this should only be used by unsafe graphics... !?

namespace LowLevelGraphics
{
    /// <summary>
    /// use this class to Draw an unsafe Ellipse or circle
    /// </summary>
    public class Bresenham
    {
        /// <summary>
        /// Draws a Horizontal Line
        /// </summary>
        /// <param name="_bitmap">The _bitmap.</param>
        /// <param name="xp">The xp.</param>
        /// <param name="yp">The yp.</param>
        /// <param name="w">The w.</param>
        /// <param name="_color">The _color.</param>
        public void HorizontalLine(IBitmap _bitmap, int xp, int yp, int w, Color _color)
        {
            for (int i = 0; i < w; i++)
            {
                _bitmap.SetPixel(xp + i, yp, _color);
            }
        }

        /// <summary>
        /// Rasters the circle.
        /// </summary>
        /// <param name="_bitmap">The _bitmap.</param>
        /// <param name="_x0">The _X0.</param>
        /// <param name="_y0">The _y0.</param>
        /// <param name="_nRadius">The _n radius.</param>
        /// <returns></returns>
        public IEnumerable<Point> RasterCircle(IBitmap _bitmap, int _x0, int _y0, int _nRadius)
        {
            List<Point> aPoint = new List<Point>();

            int f = 1 - _nRadius;
            int ddF_x = 1;
            int ddF_y = -2 * _nRadius;
            int x = 0;
            int y = _nRadius;

            aPoint.Add(new Point(_x0, _y0 + _nRadius));
            aPoint.Add(new Point(_x0, _y0 - _nRadius));
            aPoint.Add(new Point(_x0 + _nRadius, _y0));
            aPoint.Add(new Point(_x0 - _nRadius, _y0));

            while (x < y)
            {
                // ddF_x == 2 * x + 1;
                // ddF_y == -2 * y;
                // f == x*x + y*y - radius*radius + 2*x - y + 1;
                if (f >= 0)
                {
                    y--;
                    ddF_y += 2;
                    f += ddF_y;
                }
                x++;
                ddF_x += 2;
                f += ddF_x;
                aPoint.Add(new Point(_x0 + x, _y0 + y));
                aPoint.Add(new Point(_x0 - x, _y0 + y));
                aPoint.Add(new Point(_x0 + x, _y0 - y));
                aPoint.Add(new Point(_x0 - x, _y0 - y));
                aPoint.Add(new Point(_x0 + y, _y0 + x));
                aPoint.Add(new Point(_x0 - y, _y0 + x));
                aPoint.Add(new Point(_x0 + y, _y0 - x));
                aPoint.Add(new Point(_x0 - y, _y0 - x));
            }
            return aPoint;
        }

        /// <summary>
        /// Draw a rastered circle
        /// </summary>
        /// <param name="_bitmap"></param>
        /// <param name="_x0"></param>
        /// <param name="_y0"></param>
        /// <param name="_nRadius"></param>
        /// <param name="_color"></param>
        public void RasterCircle(IBitmap _bitmap, int _x0, int _y0, int _nRadius, Color _color)
        {
            int f = 1 - _nRadius;
            int ddF_x = 1;
            int ddF_y = -2 * _nRadius;
            int x = 0;
            int y = _nRadius;

            _bitmap.SetPixel(_x0, _y0 + _nRadius, _color);
            _bitmap.SetPixel(_x0, _y0 - _nRadius, _color);
            _bitmap.SetPixel(_x0 + _nRadius, _y0, _color);
            _bitmap.SetPixel(_x0 - _nRadius, _y0, _color);

            while (x < y)
            {
                // ddF_x == 2 * x + 1;
                // ddF_y == -2 * y;
                // f == x*x + y*y - radius*radius + 2*x - y + 1;
                if (f >= 0)
                {
                    y--;
                    ddF_y += 2;
                    f += ddF_y;
                }
                x++;
                ddF_x += 2;
                f += ddF_x;
                _bitmap.SetPixel(_x0 + x, _y0 + y, _color);
                _bitmap.SetPixel(_x0 - x, _y0 + y, _color);
                _bitmap.SetPixel(_x0 + x, _y0 - y, _color);
                _bitmap.SetPixel(_x0 - x, _y0 - y, _color);
                _bitmap.SetPixel(_x0 + y, _y0 + x, _color);
                _bitmap.SetPixel(_x0 - y, _y0 + x, _color);
                _bitmap.SetPixel(_x0 + y, _y0 - x, _color);
                _bitmap.SetPixel(_x0 - y, _y0 - x, _color);
            }
        }

        //TODO: Bresenham, FilledRasterCircle, TEST
        /// <summary>
        /// Filleds the raster circle.
        /// </summary>
        /// <param name="_bitmap">The _bitmap.</param>
        /// <param name="xp">The xp.</param>
        /// <param name="yp">The yp.</param>
        /// <param name="_nRadius">The _n radius.</param>
        /// <param name="col">The col.</param>
        public void FilledRasterCircle(IBitmap _bitmap, int xp, int yp, int _nRadius, Color col)
        {
            //       var canvas:BitmapData = new BitmapData(400, 400, false, 0xCCCCCC);
            //addChild(new Bitmap(canvas));
            //fillCircle(100,100,50,0xFF0000);
            int xoff = 0;
            int yoff = _nRadius;
            int balance = -_nRadius;
            while (xoff <= yoff)
            {
                int p0 = xp - xoff;
                int p1 = xp - yoff;
                int w0 = xoff + xoff;
                int w1 = yoff + yoff;
                HorizontalLine(_bitmap, p0, yp + yoff, w0, col);
                HorizontalLine(_bitmap, p0, yp - yoff, w0, col);
                HorizontalLine(_bitmap, p1, yp + xoff, w1, col);
                HorizontalLine(_bitmap, p1, yp - xoff, w1, col);
                if ((balance += xoff++ + xoff) >= 0)
                {
                    balance -= --yoff + yoff;
                }
            }
        }

        /// <summary>
        /// This function chooses an appropriate private method for rendering the line
        /// based on the begin and end characteristics. These separate methods could be
        /// combined into a single method but I believe that runtime performance while
        /// enumerating through the points would suffer. 
        /// 
        /// (given the overhead involved with the LINQ calls it may not make much difference)
        /// </summary>
        /// <param name="_ptBegin"></param>
        /// <param name="_ptEnd"></param>
        /// <returns></returns>
        public static IEnumerable<Point> RenderLine(Point _ptBegin, Point _ptEnd)
        {
            if (Math.Abs(_ptEnd.Y - _ptBegin.Y) < Math.Abs(_ptEnd.X - _ptBegin.X))
            {
                // dX > dY... not steep
                if (_ptEnd.X >= _ptBegin.X)
                {
                    return BresLineOrig(_ptBegin, _ptEnd);
                }
                else
                {
                    return BresLineReverseOrig(_ptBegin, _ptEnd);
                }
            }
            else // steep (dY > dX)
            {
                if (_ptEnd.Y >= _ptBegin.Y)
                {
                    return BresLineSteep(_ptBegin, _ptEnd);
                }
                else
                {
                    return BresLineReverseSteep(_ptBegin, _ptEnd);
                }
            }
        }

        /// <summary>
        /// Creates a line from Begin to End starting at (x0,y0) and ending at (x1,y1)
        /// * where x0 less than x1 and y0 less than y1
        ///   AND line is less steep than it is wide (dx less than dy)
        /// </summary>
        /// <param name="_ptBegin"></param>
        /// <param name="_ptEnd"></param>
        /// <returns></returns>
        protected static IEnumerable<Point> BresLineOrig(Point _ptBegin, Point _ptEnd)
        {
            Point nextPoint = _ptBegin;
            int deltax = _ptEnd.X - _ptBegin.X;
            int deltay = _ptEnd.Y - _ptBegin.Y;
            int error = deltax / 2;
            int ystep = 1;
            if (_ptEnd.Y < _ptBegin.Y)
            {
                ystep = -1;
            }
            else if (_ptEnd.Y == _ptBegin.Y)
            {
                ystep = 0;
            }

            while (nextPoint.X < _ptEnd.X)
            {
                if (nextPoint != _ptBegin) yield return nextPoint;
                nextPoint.X++;

                error -= deltay;
                if (error < 0)
                {
                    nextPoint.Y += ystep;
                    error += deltax;
                }
            }
        }

        /// <summary>
        /// Whenever dy > dx the line is considered steep and we have to change
        /// which variables we increment/decrement
        /// </summary>
        /// <param name="_ptBegin"></param>
        /// <param name="_ptEnd"></param>
        /// <returns></returns>
        private static IEnumerable<Point> BresLineSteep(Point _ptBegin, Point _ptEnd)
        {
            Point nextPoint = _ptBegin;
            int deltax = Math.Abs(_ptEnd.X - _ptBegin.X);
            int deltay = _ptEnd.Y - _ptBegin.Y;
            int error = Math.Abs(deltax / 2);
            int xstep = 1;

            if (_ptEnd.X < _ptBegin.X)
            {
                xstep = -1;
            }
            else if (_ptEnd.X == _ptBegin.X)
            {
                xstep = 0;
            }

            while (nextPoint.Y < _ptEnd.Y)
            {
                if (nextPoint != _ptBegin) yield return nextPoint;
                nextPoint.Y++;

                error -= deltax;
                if (error < 0)
                {
                    nextPoint.X += xstep;
                    error += deltay;
                }
            }
        }

        /// <summary>
        /// If x0 > x1 then we are going from right to left instead of left to right
        /// so we have to modify our routine slightly
        /// </summary>
        /// <param name="begin"></param>
        /// <param name="end"></param>
        /// <returns></returns>
        protected static IEnumerable<Point> BresLineReverseOrig(Point begin, Point end)
        {
            Point nextPoint = begin;
            int deltax = end.X - begin.X;
            int deltay = end.Y - begin.Y;
            int error = deltax / 2;
            int ystep = 1;

            if (end.Y < begin.Y)
            {
                ystep = -1;
            }
            else if (end.Y == begin.Y)
            {
                ystep = 0;
            }

            while (nextPoint.X > end.X)
            {
                if (nextPoint != begin) yield return nextPoint;
                nextPoint.X--;

                error += deltay;
                if (error < 0)
                {
                    nextPoint.Y += ystep;
                    error -= deltax;
                }
            }
        }

        /// <summary>
        /// If x0 > x1 and dy > dx we have to go from right to left and alter the routine
        /// for a steep line
        /// </summary>
        /// <param name="begin"></param>
        /// <param name="end"></param>
        /// <returns></returns>
        protected static IEnumerable<Point> BresLineReverseSteep(Point begin, Point end)
        {
            Point nextPoint = begin;
            int nDeltaX = end.X - begin.X;
            int nDeltaY = end.Y - begin.Y;
            int nError = nDeltaX / 2;
            int nXStep = 1;

            if (end.X < begin.X)
            {
                nXStep = -1;
            }
            else if (end.X == begin.X)
            {
                nXStep = 0;
            }

            while (nextPoint.Y > end.Y)
            {
                if (nextPoint != begin) yield return nextPoint;
                nextPoint.Y--;

                nError += nDeltaX;
                if (nError < 0)
                {
                    nextPoint.X += nXStep;
                    nError -= nDeltaY;
                }
            }
        }
    }
}
